#ifndef A49F4D0C_9A5C_424C_97C5_84E83BED89E1
#define A49F4D0C_9A5C_424C_97C5_84E83BED89E1
#include "types.hpp"
#include <fstream>
#include <iostream>
#include <string.h>
#include <string>

enum class Mode { None, Help, FileMode, InputMode, ArgsMode };
enum class ParseStatus { Success, Failure };

struct Params {
  FunctionType function;
  IntegralMethod method;

  double a;
  double b;
  double eps;
};

struct ParamsParsingResult {
  ParseStatus status;
  std::string message;
  Params params;
};

struct ParsingResult {
  Mode mode;
  ParseStatus status;
  std::string message;
  Params params;

  bool isOk() const { return status == ParseStatus::Success; }
  bool isFailure() const { return status == ParseStatus::Failure; }
  bool isHelp() const { return mode == Mode::Help; }
};

const std::string LOGO = R"(
             ._     __,
              |\,../'\
            ,'. .     `.
           .--         '`.
          ( `' ,          ;
          ,`--' _,       ,'\
         ,`.____            `.
        /              `,    |
       '                \,   '
       |     Lab3       /   /`,
       `,  .           ,` ./  |
       ' `.  ,'        |;,'   ,@
 ______|     |      _________,_____jv______
        `.   `.   ,'
         ,'_,','_,
         `'   `'
)";

class ArgParser {
private:
  int argc;
  char **argv;
  ParamsParsingResult read_params_from_file(char *const &filename) const {
    // Try to open file
    std::ifstream file(filename);
    if (file.fail()) {
      return {.status = ParseStatus::Failure, .message = "File not found!"};
    }

    uint function_type;
    uint integral_method;

    double a;
    double b;
    double eps;

    // Reading values from file
    file >> function_type >> integral_method >> a >> b >> eps;

    // Validate
    if (!validateFunctionType(function_type)) {
      return {.status = ParseStatus::Failure,
              .message = "Invalid function type!"};
    }
    if (!validateIntegralMethod(integral_method)) {
      return {.status = ParseStatus::Failure,
              .message = "Invalid integral method!"};
    }
    if (b <= a) {
      return {.status = ParseStatus::Failure, .message = "Invalid interval!"};
    }

    if (eps <= 0) {
      return {.status = ParseStatus::Failure, .message = "Invalid epsilon!"};
    }

    file.close();

    return {
        .status = ParseStatus::Success,
        .message = "Ok!",
        .params =
            {
                .function = static_cast<FunctionType>(function_type),
                .method = static_cast<IntegralMethod>(integral_method),
                .a = a,
                .b = b,
                .eps = eps,
            },
    };
  }

  ParamsParsingResult read_params_from_stdin() const {
    print_all_functions();
  read_ftype:
    std::cout << "---------------------------" << std::endl;
    std::cout << "Enter function type (0 - 5): ";
    int function_type;
    std::cin >> function_type;
    if (!validateFunctionType(function_type)) {
      std::cout << "Invalid function type! Try again!" << std::endl;
      goto read_ftype;
    }
    std::cout << "---------------------------" << std::endl;
    print_all_methods();
  read_itype:
    std::cout << "---------------------------" << std::endl;
    std::cout << "Enter integral method (0 - 4): ";
    int integral_method;
    std::cin >> integral_method;
    if (!validateIntegralMethod(integral_method)) {
      std::cout << "Invalid integral method! Try again!" << std::endl;
      goto read_itype;
    }
    std::cout << "---------------------------" << std::endl;

  read_interval:
    std::cout << "Enter a: ";
    double a;
    std::cin >> a;

    std::cout << "Enter b: ";
    double b;
    std::cin >> b;

    if (b <= a) {
      std::cout << "Invalid interval! Try again!" << std::endl;
      goto read_interval;
    }
    std::cout << "---------------------------" << std::endl;

    std::cout << "Enter eps: ";
    double eps;
    std::cin >> eps;
  read_eps:
    if (eps <= 0) {
      std::cout << "Invalid eps! Try again!" << std::endl;
      goto read_eps;
    }

    return {.status = ParseStatus::Success,
            .message = "Ok!",
            .params = {.function = static_cast<FunctionType>(function_type),
                       .method = static_cast<IntegralMethod>(integral_method),
                       .a = a,
                       .b = b,
                       .eps = eps}};
  }

public:
  ArgParser(int argc, char **&argv) : argc(argc), argv(argv){};
  ParsingResult parse() const {
    if (argc < 2) {
      return {.mode = Mode::None,
              .status = ParseStatus::Failure,
              .message = "Unknown mode! Please use --help or -h"};
    }
    if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
      return {
          .mode = Mode::Help,
          .status = ParseStatus::Success,
          .message =
              LOGO +
              "\nCommand list:\n--help  -h: view this command,\n--file  -f: "
              "read parameters from file,\n--args  -a: read params from "
              "command line,\n--input -i: read params from stdin"};
    }

    if (strcmp(argv[1], "--file") == 0 || strcmp(argv[1], "-f") == 0) {
      if (argc < 3) {
        return {.mode = Mode::FileMode,
                .status = ParseStatus::Failure,
                .message = "Not enough arguments! Please provide filename!"};
      }

      ParamsParsingResult result = read_params_from_file(argv[2]);
      return {.mode = Mode::FileMode,
              .status = result.status,
              .message = result.message,
              .params = result.params};
    }

    if (strcmp(argv[1], "--input") == 0 || strcmp(argv[1], "-i") == 0) {
      ParamsParsingResult result = read_params_from_stdin();
      return {.mode = Mode::InputMode,
              .status = result.status,
              .message = result.message,
              .params = result.params};
    }

    if (strcmp(argv[1], "--args") == 0 || strcmp(argv[1], "-a") == 0) {
      if (argc < 7) {
        return {.mode = Mode::ArgsMode,
                .status = ParseStatus::Failure,
                .message = "Not enough arguments! --args <function_type> "
                           "<integral_method> <a> <b> <eps>"};
      }

      uint function_type = std::atoi(argv[2]);
      uint integral_method = std::atoi(argv[3]);
      double a = std::atof(argv[4]);
      double b = std::atof(argv[5]);
      double eps = std::atof(argv[6]);

      // Validate
      if (!validateFunctionType(function_type)) {
        return {.mode = Mode::ArgsMode,
                .status = ParseStatus::Failure,
                .message = "Invalid function type!"};
      }
      if (!validateIntegralMethod(integral_method)) {
        return {.mode = Mode::ArgsMode,
                .status = ParseStatus::Failure,
                .message = "Invalid integral method!"};
      }
      if (b >= a) {
        return {.mode = Mode::ArgsMode,
                .status = ParseStatus::Failure,
                .message = "Invalid interval!"};
      }

      if (eps <= 0) {
        return {.mode = Mode::ArgsMode,
                .status = ParseStatus::Failure,
                .message = "Invalid eps!"};
      }

      return {.mode = Mode::ArgsMode,
              .status = ParseStatus::Success,
              .message = "Ok!",
              .params = {static_cast<FunctionType>(function_type),
                         static_cast<IntegralMethod>(integral_method), a, b,
                         eps}};
    }
    return {.mode = Mode::None,
            .status = ParseStatus::Failure,
            .message = "Unknown mode! Please use --help or -h"};
  };
};

#endif /* A49F4D0C_9A5C_424C_97C5_84E83BED89E1 */
